#pragma once

#include <zenoui/widgets/GraphicsItemWidget.hpp>
#include <zenoui/widgets/GraphicsViewWidget.hpp>
#include <zenoui/draws/BezierCurveDraw.hpp>
#include <zenom/GraphModel.hpp>
#include <zenox/memory.hpp>
#include <zenox/generic_hash.hpp>
#include <tsl/robin_set.h>
#include <functional>
#include <optional>
#include <any>

namespace zenoui {

struct LabelWidget;
struct GraphicsNode;
struct GraphicsGraph;
struct GraphicsPort;

struct GraphicsLink final : GraphicsItemWidget { // graphics item representing a link between two node ports
private:
    glm::vec2 m_in_pos, m_out_pos;
    zenox::weak_ptr<GraphicsPort> m_in_port = nullptr;
    mutable std::optional<BezierCurveDrawBaked> m_baked_curve;
    inline static const float k_link_curveness = 40.0f;
    inline static const float k_link_width = 4.0f;

    friend GraphicsGraph;
    friend GraphicsPort;
    friend GraphicsNode;

protected:
    /* void handle(MouseHoverEvent *event) override; */
    void handle(MouseClickEvent *event) override;
    using Widget::handle;
    void paint() override;
    std::pair<glm::vec2, glm::vec2> in_out_pos() const {
        return {m_in_pos, m_out_pos};
    }
    BezierCurveDrawBaked &get_curve() const;
    bool contains_point(glm::vec2 const &pos) const;
    void update_in_out_pos(glm::vec2 const &p1, glm::vec2 const &p2);

public:
    GraphicsLink(); // GraphicsLink has no corresponding 'link model', link is tightly coupled into port's model
    ~GraphicsLink();
};

struct GraphicsNode final : GraphicsItemWidget, zenom::NodeView { // graphics item representing a node
private:
    LabelWidget *m_label;
    std::vector<GraphicsPort *> m_ports; // ports owned by this node
    tsl::robin_set<GraphicsPort *> m_linked_out_ports; // linked to ports
    inline static const glm::vec2 k_node_size{125, 35};

    friend GraphicsGraph;
    friend GraphicsPort;
    friend GraphicsLink;

protected:
    void handle(MouseHoverEvent *event) override;
    void handle(MouseClickEvent *event) override;
    using Widget::handle;
    void paint() override;

public:
    GraphicsNode(); // GraphicsNode is created right after a NodeModel is created
    ~GraphicsNode(); // destroy? ditto

    zenom::NodeModel *model() const { return m_model; }
    void set_model(zenom::NodeModel *model); // this sets the ptr to NodeModel, should be called immed after construction
    /* void update(zenom::NodeRole role, std::any value) { // public API to set attribute (both on view and model) */
    /*     m_model->on_update(role, value); */
    /*     on_update(role, value); */
    /* } */

private:
    void on_update(zenom::NodeRole role, std::any value) override; // slot, model to view, called when some model attribute updated
    zenom::NodeModel *m_model = nullptr;
};

struct GraphicsPort final : EmptyWidget, zenom::PortView {
private:
    LabelWidget *m_label;
    GraphicsNode *m_parent_node = nullptr; // node that owns me
    GraphicsNode *m_linked_in_node = nullptr; // linked to node
    GraphicsLink *m_linked_link = nullptr; // the link it self
    // note that we must satisfy: (bool)m_linked_in_node == (bool)m_linked_link
    inline static const glm::vec2 k_port_size{20, 20};

    friend GraphicsNode;
    friend GraphicsLink;

protected:
    void handle(MouseHoverEvent *event) override;
    void handle(MouseClickEvent *event) override;
    using Widget::handle;
    void paint() override;

public:
    GraphicsPort(); // GraphicsPort is created right after a PortModel is created
    ~GraphicsPort(); // destroy? ditto

    zenom::PortModel *model() const {
        return ZENO_ASSERT(m_model);
    }
    void set_model(zenom::PortModel *model); // this sets the ptr to PortModel, should be called immed after construction
    /* void update(zenom::PortRole role, std::any value) { // public API to set attribute (both on view and model) */
    /*     m_model->on_update(role, value); */
    /*     on_update(role, value); */
    /* } */
    /* void link(GraphicsNode *node) { // public API to set attribute (both on view and model) */
    /*     ZENO_ASSERT(!m_linked_in_node); */
    /*     m_model->on_link(node->m_model); */
    /*     on_link(node->m_model); */
    /* } */
    /* void unlink() { // public API to set attribute (both on view and model) */
    /*     ZENO_ASSERT(m_linked_in_node); */
    /*     on_unlink(m_linked_in_node->m_model); */
    /*     m_model->on_unlink(); */
    /* } */

private:
    void on_update(zenom::PortRole role, std::any value) override; // slot, model to view, called when some model attribute updated
    void on_link(zenom::NodeModel *node) override; // slot, view to model, called when a new link is establisted
    void on_unlink(zenom::NodeModel *node) override; // slot, view to model, called when link is deleted
    zenom::PortModel *m_model = nullptr;
};

struct GraphicsGraph final : GraphicsViewWidget, zenom::GraphView { // graphics view widget representing a whole graph consists of nodes
protected:
    void handle(MouseClickEvent *event) override;
    void handle(KeyPressEvent *event) override;
    using Widget::handle;
    void paint() override;

    friend GraphicsNode;
    friend GraphicsLink;
    friend GraphicsPort;

public:
    GraphicsGraph(); // GraphicsGraph is created right after a GraphModel is created
    ~GraphicsGraph(); // destroy? ditto

    zenom::GraphModel *model() const { return m_model; }
    void set_model(zenom::GraphModel *model); // this sets the ptr to GraphModel, should be called immed after construction

private:
    void on_update(zenom::GraphRole role, std::any value) override; // slot, model to view, called when some model attribute updated
    void on_new_node(zenom::NodeModel *node_model) override; // called when a new node is created in model
    void on_delete_node(zenom::NodeModel *node_model) override; // called when a node is deleted from model
    zenom::GraphModel *m_model = nullptr;

    zenox::weak_ptr<GraphicsNode> m_pending_link_in_node = nullptr;
    zenox::weak_ptr<GraphicsPort> m_pending_link_out_port = nullptr;
    zenox::weak_ptr<GraphicsLink> m_pending_link = nullptr;
};

}
